package net.lgy.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import net.lgy.component.StoreEngine;
import net.lgy.controller.req.RecycleDelRequest;
import net.lgy.controller.req.RecycleRestoreRequest;
import net.lgy.dto.AccountFileDTO;
import net.lgy.enums.BizCodeEnum;
import net.lgy.enums.FolderFlagEnum;
import net.lgy.exception.BizException;
import net.lgy.mapper.AccountFileMapper;
import net.lgy.mapper.FileMapper;
import net.lgy.model.AccountFileDO;
import net.lgy.service.AccountFileService;
import net.lgy.service.RecycleService;
import net.lgy.util.SpringBeanUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 回收站服务实现类
 * 处理回收站相关的业务逻辑，包括回收站文件列表查询、文件彻底删除和文件还原等操作
 */
@Service
@Slf4j
public class RecycleServiceImpl implements RecycleService {

    @Autowired
    private AccountFileMapper accountFileMapper;

    @Autowired
    private AccountFileService accountFileService;

    @Autowired
    private FileMapper fileMapper;

    @Autowired
    private StoreEngine storeEngine;


    private static final String BUCKET_NAME = "kaelis-one";

    /**
     * 查询当前用户的回收站文件列表
     * 只显示顶层文件/文件夹（子文件不会单独显示，会随父文件夹一起展示）
     *
     * @param accountNo 用户账号ID
     * @return 回收站文件DTO列表
     */
    @Override
    public List<AccountFileDTO> listRecycleFile(Long accountNo) {
        log.info("查询用户[{}]的回收站文件列表", accountNo);

        // 查询该用户所有已删除(del=1)的文件
        List<AccountFileDO> accountFileDOS = accountFileMapper.selectRecycleFileList(accountNo, null);
        log.info("用户[{}]回收站中共找到[{}]个文件记录", accountNo, accountFileDOS.size());

        // 提取所有已删除文件的ID
        List<Long> fileIds = accountFileDOS.stream()
                .map(AccountFileDO::getId)
                .collect(Collectors.toList());

        // 过滤掉子文件（父ID在已删除文件ID列表中的文件），只保留顶层文件
        List<AccountFileDO> topLevelFiles = accountFileDOS.stream()
                .filter(file -> !fileIds.contains(file.getParentId()))
                .collect(Collectors.toList());
        log.info("用户[{}]回收站中顶层文件数量为[{}]", accountNo, topLevelFiles.size());

        // 转换为DTO并返回
        return SpringBeanUtil.copyProperties(topLevelFiles, AccountFileDTO.class);
    }

    /**
     * 彻底删除回收站中的文件
     * 支持批量删除，若删除文件夹则会递归删除其包含的所有子文件
     *
     * @param request 删除请求参数，包含用户账号和要删除的文件ID列表
     * @return 成功删除的记录数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteRecycleFile(RecycleDelRequest request) {
        Long accountNo = request.getAccountNo();
        List<Long> fileIds = request.getFileIds();
        log.info("用户[{}]请求彻底删除回收站文件，文件ID列表：{}", accountNo, fileIds);

        // 查询要删除的文件记录
        List<AccountFileDO> accountFileDOS = accountFileMapper.selectRecycleFileList(accountNo, fileIds);

        // 校验文件是否存在（请求的文件ID数量与实际查询到的数量是否一致）
        if (fileIds.size() != accountFileDOS.size()) {
            log.error("用户[{}]删除回收站文件失败，部分文件不存在，请求ID列表：{}", accountNo, fileIds);
            throw new BizException(BizCodeEnum.FILE_NOT_EXISTS);
        }

        // 递归查询所有要删除的文件（包括文件夹中的子文件）
        List<AccountFileDO> allFilesToDelete = new ArrayList<>();
        findAllAccountFileDOWithRecur(allFilesToDelete, accountFileDOS, false);
        log.info("用户[{}]彻底删除操作共涉及[{}]个文件", accountNo, allFilesToDelete.size());

        // 提取所有要删除的文件ID
        List<Long> allFileIds = allFilesToDelete.stream()
                .map(AccountFileDO::getId)
                .collect(Collectors.toList());

        // 去accountFile表中查出所有文件id的文件对应的ObjectKey
        List<AccountFileDO> accountFileDOList = accountFileMapper.selectFileList(allFileIds);

        // 通过minio删除云服务器上的文件
        for (AccountFileDO accountFileDO : accountFileDOList){
//            先判断其他的人是否有这个文件
            List<AccountFileDO> accountFileDOS1 = accountFileMapper.selectList(new QueryWrapper<AccountFileDO>()
                    .eq("file_name", accountFileDO.getFileName())
                    .eq("file_id", accountFileDO.getFileId()));

            if (accountFileDOS1.size()<=1){
                try {
                    if(accountFileDO.getObjectKey()!=null) {
                        boolean delete = storeEngine.delete(BUCKET_NAME, accountFileDO.getObjectKey());
                        if (!delete) {
                            log.error("用户[{}]删除云服务器上的文件失败，文件ObjectKey：{}", accountNo, accountFileDO.getObjectKey());
                        }
                    }

                }catch (Exception e){
                    e.printStackTrace();
                }
            }

        }

        // 删除云服务器所有文件成功

        // 执行批量删除
        int deleteCount = accountFileMapper.deleteRecylecFiles(allFileIds);
        log.info("用户[{}]彻底删除回收站文件完成，成功删除[{}]个文件", accountNo, deleteCount);

        return deleteCount;
    }

    /**
     * 还原回收站中的文件
     * 支持批量还原，处理文件名重复问题，并检查存储空间是否充足
     *
     * @param request 还原请求参数，包含用户账号和要还原的文件ID列表
     * @return 成功还原的记录数
     */
    @Override
    public int restoreRecycleFile(RecycleRestoreRequest request) {
        Long accountNo = request.getAccountNo();
        List<Long> fileIds = request.getFileIds();
        log.info("用户[{}]请求还原回收站文件，文件ID列表：{}", accountNo, fileIds);

        // 查询要还原的文件记录
        List<AccountFileDO> accountFileDOS = accountFileMapper.selectRecycleFileList(accountNo, fileIds);

        // 校验文件是否存在
        if (fileIds.size() != accountFileDOS.size()) {
            log.error("用户[{}]还原回收站文件失败，部分文件不存在，请求ID列表：{}", accountNo, fileIds);
            throw new BizException(BizCodeEnum.FILE_RECYCLE_ILLEGAL);
        }

        // 处理文件名重复问题
        accountFileDOS.forEach(file -> {
            Long duplicateCount = processFileNameDuplicate(file);
            if (duplicateCount > 0) {
                log.info("文件[{}]还原时存在重名，已重命名为：{}", file.getId(), file.getFileName());
                accountFileMapper.updateRecycleFileById(file.getId(), file.getFileName());
            }
        });

        // 递归查询所有要还原的文件（包括文件夹中的子文件）
        List<AccountFileDO> allFilesToRestore = new ArrayList<>();
        findAllAccountFileDOWithRecur(allFilesToRestore, accountFileDOS, false);
        log.info("用户[{}]还原操作共涉及[{}]个文件", accountNo, allFilesToRestore.size());

        // 提取所有要还原的文件ID
        List<Long> allFileIds = allFilesToRestore.stream()
                .map(AccountFileDO::getId)
                .collect(Collectors.toList());

        // 计算需要的存储空间总大小
        long totalSize = allFilesToRestore.stream()
                .map(file -> file.getFileSize() == null ? 0 : file.getFileSize())
                .mapToLong(Long::valueOf)
                .sum();

        // 检查并更新存储空间
        if (!accountFileService.checkAndUpdateCapacity(accountNo, totalSize)) {
            log.error("用户[{}]还原文件失败，存储空间不足，需要[{}]字节", accountNo, totalSize);
            throw new BizException(BizCodeEnum.FILE_STORAGE_NOT_ENOUGH);
        }

        // 执行批量还原（将del字段设为0）
        int restoreCount = accountFileMapper.restoreFiles(allFileIds);
        log.info("用户[{}]还原回收站文件完成，成功还原[{}]个文件", accountNo, restoreCount);

        return restoreCount;
    }

    /**
     * 递归查询所有文件（包括文件夹中的子文件）
     * 用于处理文件夹的级联操作（删除或还原）
     *
     * @param allRecords 存储所有查询到的文件记录
     * @param accountFileDOS 当前要处理的文件列表
     * @param onlyFolder 是否只处理文件夹
     */
    public void findAllAccountFileDOWithRecur(List<AccountFileDO> allRecords,
                                              List<AccountFileDO> accountFileDOS,
                                              boolean onlyFolder) {
        for (AccountFileDO file : accountFileDOS) {
            // 如果是文件夹，递归处理其子文件
            if (file.getIsDir().equals(FolderFlagEnum.YES.getCode())) {
                List<AccountFileDO> childFiles = accountFileMapper.selectDelChildFilesList(file.getId(), file.getAccountNo());
                log.debug("递归处理文件夹[{}]的子文件，共找到[{}]个子文件", file.getId(), childFiles.size());
                findAllAccountFileDOWithRecur(allRecords, childFiles, onlyFolder);
            }

            // 根据条件添加到结果列表（onlyFolder为true时只添加文件夹）
            if (!onlyFolder || file.getIsDir().equals(FolderFlagEnum.YES.getCode())) {
                allRecords.add(file);
            }
        }
    }

    /**
     * 处理文件名重复问题
     * 若同目录下存在同名文件/文件夹，在名称后添加时间戳后缀
     *
     * @param accountFileDO 账户文件关联DO对象
     * @return 重复的文件数量
     */
    public Long processFileNameDuplicate(AccountFileDO accountFileDO) {
        // 检查同目录下是否存在同名文件/文件夹（未删除状态）
        Long duplicateCount = accountFileMapper.selectCount(
                new QueryWrapper<AccountFileDO>()
                        .eq("account_no", accountFileDO.getAccountNo())
                        .eq("parent_id", accountFileDO.getParentId())
                        .eq("is_dir", accountFileDO.getIsDir())
                        .eq("file_name", accountFileDO.getFileName())
                        .eq("del", 0) // 只检查未删除的文件
        );

        // 若存在重名，处理文件名
        if (duplicateCount > 0) {
            log.warn("文件[{}]在目录[{}]下存在[{}]个同名文件，将进行重命名",
                    accountFileDO.getFileName(), accountFileDO.getParentId(), duplicateCount);

            // 处理文件夹重名
            if (accountFileDO.getIsDir().equals(FolderFlagEnum.YES.getCode())) {
                accountFileDO.setFileName(accountFileDO.getFileName() + "_" + System.currentTimeMillis());
            }
            // 处理文件重名（保留扩展名）
            else {
                String[] fileNameParts = accountFileDO.getFileName().split("\\.");
                // 处理没有扩展名的文件
                if (fileNameParts.length == 1) {
                    accountFileDO.setFileName(fileNameParts[0] + "_" + System.currentTimeMillis());
                } else {
                    // 拼接新文件名：名称_时间戳.扩展名
                    StringBuilder newFileName = new StringBuilder();
                    for (int i = 0; i < fileNameParts.length - 1; i++) {
                        newFileName.append(fileNameParts[i]);
                    }
                    newFileName.append("_").append(System.currentTimeMillis())
                            .append(".").append(fileNameParts[fileNameParts.length - 1]);
                    accountFileDO.setFileName(newFileName.toString());
                }
            }
        }

        return duplicateCount;
    }
}